/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;

using Directory = Lucene.Net.Store.Directory;
using IndexOutput = Lucene.Net.Store.IndexOutput;
using StringHelper = Lucene.Net.Util.StringHelper;
using UnicodeUtil = Lucene.Net.Util.UnicodeUtil;

namespace Lucene.Net.Index
{
    sealed class TermVectorsWriter : IDisposable
    {
        
        private readonly IndexOutput tvx = null;
        private readonly IndexOutput tvd = null;
        private readonly IndexOutput tvf = null;
        private readonly FieldInfos fieldInfos;
        internal UnicodeUtil.UTF8Result[] utf8Results = new[]{new UnicodeUtil.UTF8Result(), new UnicodeUtil.UTF8Result()};
        
        public TermVectorsWriter(Directory directory, System.String segment, FieldInfos fieldInfos)
        {
            // Open files for TermVector storage
            tvx = directory.CreateOutput(segment + "." + IndexFileNames.VECTORS_INDEX_EXTENSION);
            tvx.WriteInt(TermVectorsReader.FORMAT_CURRENT);
            tvd = directory.CreateOutput(segment + "." + IndexFileNames.VECTORS_DOCUMENTS_EXTENSION);
            tvd.WriteInt(TermVectorsReader.FORMAT_CURRENT);
            tvf = directory.CreateOutput(segment + "." + IndexFileNames.VECTORS_FIELDS_EXTENSION);
            tvf.WriteInt(TermVectorsReader.FORMAT_CURRENT);
            
            this.fieldInfos = fieldInfos;
        }
        
        /// <summary> Add a complete document specified by all its term vectors. If document has no
        /// term vectors, add value for tvx.
        /// 
        /// </summary>
        /// <param name="vectors">
        /// </param>
        /// <throws>  IOException </throws>
        public void  AddAllDocVectors(ITermFreqVector[] vectors)
        {
            
            tvx.WriteLong(tvd.FilePointer);
            tvx.WriteLong(tvf.FilePointer);
            
            if (vectors != null)
            {
                int numFields = vectors.Length;
                tvd.WriteVInt(numFields);
                
                var fieldPointers = new long[numFields];
                
                for (int i = 0; i < numFields; i++)
                {
                    fieldPointers[i] = tvf.FilePointer;
                    
                    int fieldNumber = fieldInfos.FieldNumber(vectors[i].Field);
                    
                    // 1st pass: write field numbers to tvd
                    tvd.WriteVInt(fieldNumber);
                    
                    int numTerms = vectors[i].Size;
                    tvf.WriteVInt(numTerms);
                    
                    TermPositionVector tpVector;
                    
                    byte bits;
                    bool storePositions;
                    bool storeOffsets;
                    
                    if (vectors[i] is TermPositionVector)
                    {
                        // May have positions & offsets
                        tpVector = (TermPositionVector) vectors[i];
                        storePositions = tpVector.Size > 0 && tpVector.GetTermPositions(0) != null;
                        storeOffsets = tpVector.Size > 0 && tpVector.GetOffsets(0) != null;
                        bits = (byte) ((storePositions?TermVectorsReader.STORE_POSITIONS_WITH_TERMVECTOR: (byte) 0) + (storeOffsets?TermVectorsReader.STORE_OFFSET_WITH_TERMVECTOR: (byte) 0));
                    }
                    else
                    {
                        tpVector = null;
                        bits = 0;
                        storePositions = false;
                        storeOffsets = false;
                    }
                    
                    tvf.WriteVInt(bits);
                    
                    System.String[] terms = vectors[i].GetTerms();
                    int[] freqs = vectors[i].GetTermFrequencies();
                    
                    int utf8Upto = 0;
                    utf8Results[1].length = 0;
                    
                    for (int j = 0; j < numTerms; j++)
                    {
                        
                        UnicodeUtil.UTF16toUTF8(terms[j], 0, terms[j].Length, utf8Results[utf8Upto]);
                        
                        int start = StringHelper.BytesDifference(utf8Results[1 - utf8Upto].result, utf8Results[1 - utf8Upto].length, utf8Results[utf8Upto].result, utf8Results[utf8Upto].length);
                        int length = utf8Results[utf8Upto].length - start;
                        tvf.WriteVInt(start); // write shared prefix length
                        tvf.WriteVInt(length); // write delta length
                        tvf.WriteBytes(utf8Results[utf8Upto].result, start, length); // write delta bytes
                        utf8Upto = 1 - utf8Upto;
                        
                        int termFreq = freqs[j];
                        
                        tvf.WriteVInt(termFreq);
                        
                        if (storePositions)
                        {
                            int[] positions = tpVector.GetTermPositions(j);
                            if (positions == null)
                                throw new System.SystemException("Trying to write positions that are null!");
                            System.Diagnostics.Debug.Assert(positions.Length == termFreq);
                            
                            // use delta encoding for positions
                            int lastPosition = 0;
                            foreach (int position in positions)
                            {
                                tvf.WriteVInt(position - lastPosition);
                                lastPosition = position;
                            }
                        }
                        
                        if (storeOffsets)
                        {
                            TermVectorOffsetInfo[] offsets = tpVector.GetOffsets(j);
                            if (offsets == null)
                                throw new System.SystemException("Trying to write offsets that are null!");
                            System.Diagnostics.Debug.Assert(offsets.Length == termFreq);
                            
                            // use delta encoding for offsets
                            int lastEndOffset = 0;
                            foreach (TermVectorOffsetInfo t in offsets)
                            {
                                int startOffset = t.StartOffset;
                                int endOffset = t.EndOffset;
                                tvf.WriteVInt(startOffset - lastEndOffset);
                                tvf.WriteVInt(endOffset - startOffset);
                                lastEndOffset = endOffset;
                            }
                        }
                    }
                }
                
                // 2nd pass: write field pointers to tvd
                if (numFields > 1)
                {
                    long lastFieldPointer = fieldPointers[0];
                    for (int i = 1; i < numFields; i++)
                    {
                        long fieldPointer = fieldPointers[i];
                        tvd.WriteVLong(fieldPointer - lastFieldPointer);
                        lastFieldPointer = fieldPointer;
                    }
                }
            }
            else
                tvd.WriteVInt(0);
        }
        
        /// <summary> Do a bulk copy of numDocs documents from reader to our
        /// streams.  This is used to expedite merging, if the
        /// field numbers are congruent.
        /// </summary>
        internal void  AddRawDocuments(TermVectorsReader reader, int[] tvdLengths, int[] tvfLengths, int numDocs)
        {
            long tvdPosition = tvd.FilePointer;
            long tvfPosition = tvf.FilePointer;
            long tvdStart = tvdPosition;
            long tvfStart = tvfPosition;
            for (int i = 0; i < numDocs; i++)
            {
                tvx.WriteLong(tvdPosition);
                tvdPosition += tvdLengths[i];
                tvx.WriteLong(tvfPosition);
                tvfPosition += tvfLengths[i];
            }
            tvd.CopyBytes(reader.GetTvdStream(), tvdPosition - tvdStart);
            tvf.CopyBytes(reader.GetTvfStream(), tvfPosition - tvfStart);
            System.Diagnostics.Debug.Assert(tvd.FilePointer == tvdPosition);
            System.Diagnostics.Debug.Assert(tvf.FilePointer == tvfPosition);
        }
        
        /// <summary>Close all streams. </summary>
        public void Dispose()
        {
            // Move to a protected method if class becomes unsealed

            // make an effort to close all streams we can but remember and re-throw
            // the first exception encountered in this process
            System.IO.IOException keep = null;
            if (tvx != null)
                try
                {
                    tvx.Close();
                }
                catch (System.IO.IOException e)
                {
                    keep = e;
                }
            if (tvd != null)
                try
                {
                    tvd.Close();
                }
                catch (System.IO.IOException e)
                {
                    if (keep == null)
                        keep = e;
                }
            if (tvf != null)
                try
                {
                    tvf.Close();
                }
                catch (System.IO.IOException e)
                {
                    if (keep == null)
                        keep = e;
                }
            if (keep != null)
            {
                throw new System.IO.IOException(keep.StackTrace);
            }
        }
    }
}